1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636 |
- <template>
- <div v-if="workflow" class="flex h-screen">
- <div
- v-if="state.showSidebar && haveEditAccess"
- class="w-80 bg-white dark:bg-gray-800 py-6 relative border-l border-gray-100 dark:border-gray-700 dark:border-opacity-50 flex flex-col"
- >
- <workflow-edit-block
- v-if="editState.editing"
- :data="editState.blockData"
- :workflow="workflow"
- :editor="editor"
- @update="updateBlockData"
- @close="(editState.editing = false), (editState.blockData = {})"
- />
- <workflow-details-card
- v-else
- :workflow="workflow"
- @update="updateWorkflow"
- />
- </div>
- <div class="flex-1 relative overflow-auto">
- <div
- class="absolute w-full flex items-center z-10 left-0 p-4 top-0 pointer-events-none"
- >
- <ui-card
- v-if="!haveEditAccess"
- padding="px-2 mr-4"
- class="flex items-center overflow-hidden"
- style="min-width: 150px; height: 48px"
- >
- <span class="inline-block">
- <ui-img
- v-if="workflow.icon.startsWith('http')"
- :src="workflow.icon"
- class="w-8 h-8"
- />
- <v-remixicon v-else :name="workflow.icon" size="26" />
- </span>
- <div class="ml-2 max-w-sm">
- <p
- :class="{ 'text-lg': !workflow.description }"
- class="font-semibold leading-tight text-overflow"
- >
- {{ workflow.name }}
- </p>
- <p
- :class="{ 'text-sm': workflow.description }"
- class="text-gray-600 leading-tight dark:text-gray-200 text-overflow"
- >
- {{ workflow.description }}
- </p>
- </div>
- </ui-card>
- <ui-tabs
- v-model="state.activeTab"
- class="border-none px-2 rounded-lg h-full space-x-1 bg-white dark:bg-gray-800 pointer-events-auto"
- >
- <button
- v-if="haveEditAccess"
- v-tooltip="
- `${t('workflow.toggleSidebar')} (${
- shortcut['editor:toggle-sidebar'].readable
- })`
- "
- style="margin-right: 6px"
- @click="toggleSidebar"
- >
- <v-remixicon
- :name="state.showSidebar ? 'riSideBarFill' : 'riSideBarLine'"
- />
- </button>
- <ui-tab value="editor">{{ t('common.editor') }}</ui-tab>
- <template v-if="isPackage">
- <ui-tab value="package-details"> Details </ui-tab>
- <ui-tab value="package-settings">
- {{ t('common.settings') }}
- </ui-tab>
- </template>
- <ui-tab v-else value="logs" class="flex items-center">
- {{ t('common.log', 2) }}
- <span
- v-if="workflowStates.length > 0"
- class="ml-2 p-1 text-center inline-block text-xs rounded-full bg-accent text-white dark:text-black"
- style="min-width: 25px"
- >
- {{ workflowStates.length }}
- </span>
- </ui-tab>
- </ui-tabs>
- <ui-card v-if="isTeamWorkflow" padding="p-1 ml-4 pointer-events-auto">
- <ui-input
- v-tooltip="'Workflow URL'"
- prepend-icon="riLinkM"
- :model-value="`https://automa.site/teams/${teamId}/workflows/${workflow.id}`"
- readonly
- @click="$event.target.select()"
- />
- </ui-card>
- <div class="flex-grow pointer-events-none" />
- <editor-used-credentials v-if="editor" :editor="editor" />
- <template v-if="isPackage">
- <ui-button
- v-if="workflow.isExternal"
- v-tooltip="t('workflow.previewMode.description')"
- class="pointer-events-auto cursor-default"
- >
- <v-remixicon name="riEyeLine" class="mr-2 -ml-1" />
- <span>{{ t('workflow.previewMode.title') }}</span>
- </ui-button>
- <editor-pkg-actions
- v-else
- :editor="editor"
- :data="workflow"
- :is-data-changed="state.dataChanged"
- @update="onActionUpdated"
- />
- </template>
- <editor-local-actions
- v-else
- :editor="editor"
- :workflow="workflow"
- :is-data-changed="state.dataChanged"
- :is-team="isTeamWorkflow"
- :is-package="isPackage"
- :can-edit="haveEditAccess"
- @update="onActionUpdated"
- @permission="checkWorkflowPermission"
- @modal="(modalState.name = $event), (modalState.show = true)"
- />
- </div>
- <ui-tab-panels
- v-model="state.activeTab"
- :class="{ 'overflow-hidden': !state.activeTab.startsWith('package') }"
- class="h-full w-full"
- @drop="onDropInEditor"
- @dragend="clearHighlightedElements"
- @dragover.prevent="onDragoverEditor"
- >
- <template v-if="isPackage">
- <ui-tab-panel value="package-details" class="pt-24 container">
- <package-details :data="workflow" @update="updateWorkflow" />
- </ui-tab-panel>
- <ui-tab-panel value="package-settings" class="pt-24 container">
- <package-settings
- :data="workflow"
- :editor="editor"
- @update="updateWorkflow"
- @goBlock="goToPkgBlock"
- />
- </ui-tab-panel>
- </template>
- <ui-tab-panel cache value="editor" class="w-full">
- <workflow-editor
- v-if="state.workflowConverted"
- :id="route.params.id"
- :data="editorData"
- :disabled="isTeamWorkflow && !haveEditAccess"
- :class="{ 'animate-blocks': state.animateBlocks }"
- class="h-screen focus:outline-none workflow-editor"
- tabindex="0"
- @init="onEditorInit"
- @edit="initEditBlock"
- @update:node="state.dataChanged = true"
- @delete:node="state.dataChanged = true"
- >
- <template
- v-if="!isTeamWorkflow || haveEditAccess"
- #controls-prepend
- >
- <ui-card padding="p-0 ml-2 undo-redo">
- <button
- v-tooltip.group="
- `${t('workflow.undo')} (${getReadableShortcut('mod+z')})`
- "
- :disabled="!commandManager.state.value.canUndo"
- class="p-2 rounded-lg transition-colors"
- @click="executeCommand('undo')"
- >
- <v-remixicon name="riArrowGoBackLine" />
- </button>
- <button
- v-tooltip.group="
- `${t('workflow.redo')} (${getReadableShortcut(
- 'mod+shift+z'
- )})`
- "
- :disabled="!commandManager.state.value.canRedo"
- class="p-2 rounded-lg transition-colors"
- @click="executeCommand('redo')"
- >
- <v-remixicon name="riArrowGoForwardLine" />
- </button>
- </ui-card>
- <button
- v-if="!isPackage && haveEditAccess"
- v-tooltip="t('packages.open')"
- class="control-button hoverable ml-2"
- @click="blockFolderModal.showList = !blockFolderModal.showList"
- >
- <v-remixicon name="mdiPackageVariantClosed" />
- </button>
- <button
- v-tooltip="t('workflow.autoAlign.title')"
- class="control-button hoverable ml-2"
- @click="autoAlign"
- >
- <v-remixicon name="riMagicLine" />
- </button>
- </template>
- </workflow-editor>
- <editor-local-saved-blocks
- v-if="blockFolderModal.showList"
- @close="blockFolderModal.showList = false"
- />
- <editor-local-ctx-menu
- v-if="editor"
- :editor="editor"
- :is-package="isPackage"
- :package-io="workflow.settings?.asBlock"
- @group="groupBlocks"
- @ungroup="ungroupBlocks"
- @copy="copySelectedElements"
- @paste="pasteCopiedElements"
- @saveBlock="initBlockFolder"
- @duplicate="duplicateElements"
- @packageIo="addPackageIO"
- />
- </ui-tab-panel>
- <ui-tab-panel value="logs" class="mt-24 container">
- <editor-logs
- :workflow-id="route.params.id"
- :workflow-states="workflowStates"
- />
- </ui-tab-panel>
- </ui-tab-panels>
- </div>
- </div>
- <ui-modal
- v-model="modalState.show"
- :content-class="activeWorkflowModal?.width || 'max-w-xl'"
- v-bind="activeWorkflowModal.attrs || {}"
- >
- <template v-if="activeWorkflowModal.title" #header>
- {{ activeWorkflowModal.title }}
- <a
- v-if="activeWorkflowModal.docs"
- :title="t('common.docs')"
- :href="activeWorkflowModal.docs"
- target="_blank"
- class="inline-block align-middle"
- >
- <v-remixicon name="riInformationLine" size="20" />
- </a>
- </template>
- <component
- :is="activeWorkflowModal.component"
- v-bind="{ workflow }"
- v-on="activeWorkflowModal?.events || {}"
- @update="updateWorkflow"
- @close="modalState.show = false"
- />
- </ui-modal>
- <shared-permissions-modal
- v-model="permissionState.showModal"
- :permissions="permissionState.items"
- @granted="registerTrigger"
- />
- <ui-modal v-model="blockFolderModal.showModal" :title="t('packages.set')">
- <editor-add-package
- :data="{
- name: blockFolderModal.name,
- description: blockFolderModal.description,
- icon: blockFolderModal.icon,
- }"
- @update="Object.assign(blockFolderModal, $event)"
- @cancel="clearBlockFolderModal"
- @add="saveBlockToFolder"
- />
- </ui-modal>
- </template>
- <script setup>
- import {
- watch,
- provide,
- markRaw,
- reactive,
- computed,
- onMounted,
- shallowRef,
- onBeforeUnmount,
- } from 'vue';
- import cloneDeep from 'lodash.clonedeep';
- import { useI18n } from 'vue-i18n';
- import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
- import { customAlphabet } from 'nanoid';
- import { useToast } from 'vue-toastification';
- import { useHead } from '@vueuse/head';
- import defu from 'defu';
- import dagre from 'dagre';
- import { useUserStore } from '@/stores/user';
- import { usePackageStore } from '@/stores/package';
- import { useWorkflowStore } from '@/stores/workflow';
- import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
- import {
- useShortcut,
- getShortcut,
- getReadableShortcut,
- } from '@/composable/shortcut';
- import { getWorkflowPermissions } from '@/utils/workflowData';
- import { fetchApi } from '@/utils/api';
- import { getBlocks } from '@/utils/getSharedData';
- import { excludeGroupBlocks } from '@/utils/shared';
- import { functions } from '@/utils/referenceData/mustacheReplacer';
- import { useGroupTooltip } from '@/composable/groupTooltip';
- import { useCommandManager } from '@/composable/commandManager';
- import { debounce, parseJSON, throttle } from '@/utils/helper';
- import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
- import browser from 'webextension-polyfill';
- import dbStorage from '@/db/storage';
- import DroppedNode from '@/utils/editor/DroppedNode';
- import EditorCommands from '@/utils/editor/EditorCommands';
- import convertWorkflowData from '@/utils/convertWorkflowData';
- import extractAutocopmleteData from '@/utils/editor/editorAutocomplete';
- import WorkflowShare from '@/components/newtab/workflow/WorkflowShare.vue';
- import WorkflowEditor from '@/components/newtab/workflow/WorkflowEditor.vue';
- import WorkflowSettings from '@/components/newtab/workflow/WorkflowSettings.vue';
- import WorkflowShareTeam from '@/components/newtab/workflow/WorkflowShareTeam.vue';
- import WorkflowEditBlock from '@/components/newtab/workflow/WorkflowEditBlock.vue';
- import WorkflowDataTable from '@/components/newtab/workflow/WorkflowDataTable.vue';
- import WorkflowGlobalData from '@/components/newtab/workflow/WorkflowGlobalData.vue';
- import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
- import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
- import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';
- import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
- import EditorPkgActions from '@/components/newtab/workflow/editor/EditorPkgActions.vue';
- import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
- import EditorLocalActions from '@/components/newtab/workflow/editor/EditorLocalActions.vue';
- import EditorUsedCredentials from '@/components/newtab/workflow/editor/EditorUsedCredentials.vue';
- import EditorLocalSavedBlocks from '@/components/newtab/workflow/editor/EditorLocalSavedBlocks.vue';
- import PackageDetails from '@/components/newtab/package/PackageDetails.vue';
- import PackageSettings from '@/components/newtab/package/PackageSettings.vue';
- const blocks = getBlocks();
- let editorCommands = null;
- const executeCommandTimeout = null;
- const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 7);
- useGroupTooltip();
- const { t, te } = useI18n();
- const toast = useToast();
- const route = useRoute();
- const router = useRouter();
- const userStore = useUserStore();
- const packageStore = usePackageStore();
- const workflowStore = useWorkflowStore();
- const commandManager = useCommandManager();
- const teamWorkflowStore = useTeamWorkflowStore();
- const { teamId, id: workflowId } = route.params;
- const isTeamWorkflow = route.name === 'team-workflows';
- const isPackage = route.name === 'packages-details';
- const funcsAutocomplete = Object.keys(functions).reduce((acc, name) => {
- acc[`$${name}`] = '';
- return acc;
- }, {});
- const editor = shallowRef(null);
- const connectedTable = shallowRef(null);
- const state = reactive({
- showSidebar: true,
- dataChanged: false,
- animateBlocks: false,
- isExecuteCommand: false,
- workflowConverted: false,
- activeTab: route.query.tab || 'editor',
- });
- const blockFolderModal = reactive({
- name: '',
- icon: '',
- nodes: [],
- description: '',
- showList: false,
- showModal: false,
- });
- const permissionState = reactive({
- permissions: [],
- showModal: false,
- });
- const modalState = reactive({
- name: '',
- show: false,
- });
- const editState = reactive({
- blockData: {},
- editing: false,
- });
- const autocompleteState = reactive({
- blocks: {},
- common: {},
- });
- const workflowPayload = {
- data: {},
- isUpdating: false,
- };
- const workflowModals = {
- table: {
- icon: 'riKey2Line',
- width: 'max-w-2xl',
- component: WorkflowDataTable,
- title: t('workflow.table.title'),
- docs: 'https://docs.automa.site/api-reference/table.html',
- events: {
- /* eslint-disable-next-line */
- connect: fetchConnectedTable,
- disconnect() {
- connectedTable.value = null;
- },
- },
- },
- 'workflow-share': {
- icon: 'riShareLine',
- component: WorkflowShare,
- attrs: {
- blur: true,
- persist: true,
- customContent: true,
- },
- events: {
- close() {
- modalState.show = false;
- modalState.name = '';
- },
- publish() {
- modalState.show = false;
- modalState.name = '';
- },
- },
- },
- 'workflow-share-team': {
- icon: 'riShareLine',
- component: WorkflowShareTeam,
- attrs: {
- blur: true,
- persist: true,
- customContent: true,
- },
- events: {
- close() {
- modalState.show = false;
- modalState.name = '';
- },
- publish() {
- modalState.show = false;
- modalState.name = '';
- },
- },
- },
- 'global-data': {
- width: 'max-w-2xl',
- icon: 'riDatabase2Line',
- component: WorkflowGlobalData,
- title: t('common.globalData'),
- docs: 'https://docs.automa.site/api-reference/global-data.html',
- },
- settings: {
- width: 'max-w-2xl',
- icon: 'riSettings3Line',
- component: WorkflowSettings,
- title: t('common.settings'),
- attrs: {
- customContent: true,
- },
- events: {
- close() {
- modalState.show = false;
- modalState.name = '';
- },
- },
- },
- };
- const autocompleteList = computed(() => {
- const autocompleteData = {
- loopData: {},
- googleSheets: {},
- table: {},
- ...funcsAutocomplete,
- globalData: autocompleteState.common.globalData,
- variables: { ...autocompleteState.common.variables },
- };
- Object.values(autocompleteState.blocks).forEach((item) => {
- if (item.loopData) Object.assign(autocompleteData.loopData, item.loopData);
- if (item.variables)
- Object.assign(autocompleteData.variables, item.variables);
- if (item.googleSheets)
- Object.assign(autocompleteData.googleSheets, item.googleSheets);
- });
- return autocompleteData;
- });
- const haveEditAccess = computed(() => {
- if (!isTeamWorkflow) return true;
- return userStore.validateTeamAccess(teamId, ['edit', 'owner', 'create']);
- });
- const workflow = computed(() => {
- if (isTeamWorkflow) {
- return teamWorkflowStore.getById(teamId, workflowId);
- }
- if (isPackage) {
- return packageStore.getById(workflowId);
- }
- return workflowStore.getById(workflowId);
- });
- const workflowStates = computed(() =>
- workflowStore.getWorkflowStates(route.params.id)
- );
- const activeWorkflowModal = computed(
- () => workflowModals[modalState.name] || {}
- );
- const workflowColumns = computed(() => {
- if (connectedTable.value) {
- return connectedTable.value.columns;
- }
- return workflow.value.table;
- });
- const editorData = computed(() => {
- if (isPackage) return workflow.value.data;
- return workflow.value.drawflow;
- });
- provide('workflow', {
- editState,
- data: workflow,
- columns: workflowColumns,
- });
- provide('workflow-editor', editor);
- provide('autocompleteData', autocompleteList);
- const updateBlockData = debounce((data) => {
- if (!haveEditAccess.value) return;
- const node = editor.value.getNode.value(editState.blockData.blockId);
- const dataCopy = JSON.parse(JSON.stringify(data));
- let autocompleteId = '';
- if (editState.blockData.itemId) {
- const itemIndex = node.data.blocks.findIndex(
- ({ itemId }) => itemId === editState.blockData.itemId
- );
- if (itemIndex !== -1) {
- node.data.blocks[itemIndex].data = dataCopy;
- autocompleteId = editState.blockData.itemId;
- }
- } else {
- node.data = dataCopy;
- autocompleteId = editState.blockData.blockId;
- }
- if (autocompleteState.blocks[autocompleteId]) {
- const { id, blockId } = editState.blockData;
- Object.assign(
- autocompleteState.blocks,
- /* eslint-disable-next-line */
- extractAutocopmleteData(id, { data, id: blockId })
- );
- }
- editState.blockData.data = data;
- state.dataChanged = true;
- }, 250);
- const updateHostedWorkflow = throttle(async () => {
- if (isTeamWorkflow) return;
- if (!userStore.user || workflowPayload.isUpdating) return;
- const isHosted = userStore.hostedWorkflows[route.params.id];
- const isBackup = userStore.backupIds.includes(route.params.id);
- const workflowExist = workflowStore.getById(route.params.id);
- if (
- (!isBackup && !isHosted) ||
- !workflowExist ||
- Object.keys(workflowPayload.data).length === 0
- )
- return;
- workflowPayload.isUpdating = true;
- const delKeys = [
- 'id',
- 'pass',
- 'logs',
- 'trigger',
- 'createdAt',
- 'isDisabled',
- 'isProtected',
- ];
- delKeys.forEach((key) => {
- delete workflowPayload.data[key];
- });
- try {
- if (typeof workflowPayload.data.drawflow === 'string') {
- workflowPayload.data.drawflow = parseJSON(
- workflowPayload.data.drawflow,
- workflowPayload.data.drawflow
- );
- }
- const response = await fetchApi(`/me/workflows/${route.params.id}`, {
- method: 'PUT',
- keepalive: true,
- body: JSON.stringify({
- workflow: workflowPayload.data,
- }),
- });
- if (!response.ok) throw new Error(response.message);
- if (isBackup) {
- const result = await response.json();
- if (result.updatedAt) {
- await browser.storage.local.set({ lastBackup: result.updatedAt });
- }
- }
- workflowPayload.data = {};
- workflowPayload.isUpdating = false;
- } catch (error) {
- console.error(error);
- workflowPayload.isUpdating = false;
- }
- }, 5000);
- const onEdgesChange = debounce((changes) => {
- // const edgeChanges = { added: [], removed: [] };
- changes.forEach(({ type }) => {
- // if (type === 'remove') {
- // edgeChanges.removed.push(id);
- // } else if (type === 'add') {
- // edgeChanges.added.push(item);
- // }
- if (state.dataChanged) return;
- state.dataChanged = type !== 'select';
- });
- // if (state.isExecuteCommand) return;
- // let command = null;
- // if (edgeChanges.added.length > 0) {
- // command = editorCommands.edgeAdded(edgeChanges.added);
- // } else if (edgeChanges.removed.length > 0) {
- // command = editorCommands.edgeRemoved(edgeChanges.removed);
- // }
- // if (command) commandManager.add(command);
- }, 250);
- let nodeTargetHandle = null;
- function onClickEditor({ target }) {
- const targetClass = target.classList;
- const isHandle = targetClass.contains('vue-flow__handle');
- const clearActiveTarget = () => {
- if (nodeTargetHandle) {
- const targetEl = document.querySelector(
- `.vue-flow__handle[data-handleid="${nodeTargetHandle.handleId}"]`
- );
- if (targetEl) targetEl.classList.remove('ring-2');
- }
- };
- if (!isHandle) {
- clearActiveTarget();
- nodeTargetHandle = null;
- return;
- }
- if (nodeTargetHandle && targetClass.contains('target')) {
- const { handleid, nodeid } = target.dataset;
- const connectionExist = document.querySelector(
- `.vue-flow__edge.target-${handleid}.source-${nodeTargetHandle.handleId}`
- );
- if (!connectionExist) {
- editor.value.addEdges([
- {
- source: nodeid,
- sourceHandle: handleid,
- target: nodeTargetHandle.nodeId,
- targetHandle: nodeTargetHandle.handleId,
- },
- ]);
- }
- clearActiveTarget();
- nodeTargetHandle = null;
- } else {
- clearActiveTarget();
- target.classList.add('ring-2');
- const { handleid, nodeid } = target.dataset;
- nodeTargetHandle = { nodeId: nodeid, handleId: handleid };
- }
- }
- function goToBlock(blockId) {
- if (!editor.value) return;
- const block = editor.value.getNode.value(blockId);
- if (!block) return;
- editor.value.addSelectedNodes([block]);
- setTimeout(() => {
- const editorContainer = document.querySelector('.vue-flow');
- const { height, width } = editorContainer.getBoundingClientRect();
- const { x, y } = block.position;
- editor.value.setTransform({
- y: -(y - height / 2),
- x: -(x - width / 2) - 200,
- zoom: 1,
- });
- }, 200);
- }
- function goToPkgBlock(blockId) {
- state.activeTab = 'editor';
- goToBlock(blockId);
- }
- function addPackageIO({ type, handleId, nodeId }) {
- const copyPkgIO = [...workflow.value[type]];
- const itemExist = copyPkgIO.some(
- (io) => io.blockId === nodeId && handleId === io.handleId
- );
- if (itemExist) {
- toast.error(`You already add this as an ${type.slice(0, -1)}`);
- return;
- }
- copyPkgIO.push({
- handleId,
- name: '',
- id: nanoid(),
- blockId: nodeId,
- });
- /* eslint-disable-next-line */
- updateWorkflow({ [type]: copyPkgIO });
- }
- function initBlockFolder({ nodes }) {
- Object.assign(blockFolderModal, {
- nodes,
- showModal: true,
- });
- }
- function clearBlockFolderModal() {
- Object.assign(blockFolderModal, {
- name: '',
- nodes: [],
- asBlock: false,
- description: '',
- showModal: false,
- icon: 'mdiPackageVariantClosed',
- });
- }
- async function saveBlockToFolder() {
- try {
- const seen = new Set();
- const nodeList = [
- ...editor.value.getSelectedNodes.value,
- ...blockFolderModal.nodes,
- ].reduce((acc, node) => {
- if (seen.has(node.id)) return acc;
- const { label, data, position, id, type } = node;
- acc.push(cloneDeep({ label, data, position, id, type }));
- seen.add(node.id);
- return acc;
- }, []);
- const edges = editor.value.getSelectedEdges.value.map(
- ({ source, target, targetHandle, sourceHandle, id }) =>
- cloneDeep({ id, source, target, targetHandle, sourceHandle })
- );
- packageStore.insert({
- data: { nodes: nodeList, edges },
- name: blockFolderModal.name || 'unnamed',
- description: blockFolderModal.description,
- asBlock: blockFolderModal?.asBlock ?? false,
- icon: blockFolderModal.icon || 'mdiPackageVariantClosed',
- });
- clearBlockFolderModal();
- } catch (error) {
- console.error(error);
- }
- }
- function groupBlocks({ position }) {
- const nodesToDelete = [];
- const nodes = editor.value.getSelectedNodes.value;
- const groupBlocksList = nodes.reduce((acc, node) => {
- if (excludeGroupBlocks.includes(node.label)) return acc;
- acc.push({
- id: node.label,
- itemId: node.id,
- data: node.data,
- });
- nodesToDelete.push(node);
- return acc;
- }, []);
- if (groupBlocksList.length === 0) return;
- editor.value.removeNodes(nodesToDelete);
- const { component, data } = blocks['blocks-group'];
- editor.value.addNodes([
- {
- id: nanoid(),
- type: component,
- label: 'blocks-group',
- data: { ...data, blocks: groupBlocksList },
- position: editor.value.project({
- x: position.clientX - 360,
- y: position.clientY,
- }),
- },
- ]);
- }
- function ungroupBlocks({ nodes }) {
- const [node] = nodes;
- if (!node || node.label !== 'blocks-group') return;
- const edges = [];
- const position = { ...node.position };
- const copyBlocks = cloneDeep(node.data?.blocks || []);
- const groupBlocksList = copyBlocks.map((item, index) => {
- const nextNode = copyBlocks[index + 1];
- if (nextNode) {
- edges.push({
- source: item.itemId,
- target: nextNode.itemId,
- sourceHandle: `${item.itemId}-output-1`,
- targetHandle: `${nextNode.itemId}-input-1`,
- });
- }
- item.label = item.id;
- item.id = item.itemId;
- item.position = { ...position };
- item.type = blocks[item.label].component;
- delete item.itemId;
- position.x += 250;
- return item;
- });
- editor.value.removeNodes(nodes);
- editor.value.addNodes(groupBlocksList);
- editor.value.addSelectedNodes(groupBlocksList);
- editor.value.addEdges(edges);
- }
- async function initAutocomplete() {
- const autocompleteCache = sessionStorage.getItem(
- `autocomplete:${workflowId}`
- );
- if (autocompleteCache) {
- const objData = parseJSON(autocompleteCache, {});
- autocompleteState.blocks = objData;
- } else {
- const autocompleteData = {};
- editorData.value.nodes.forEach(({ label, id, data }) => {
- Object.assign(
- autocompleteData,
- extractAutocopmleteData(label, { data, id })
- );
- });
- autocompleteState.blocks = autocompleteData;
- }
- try {
- const storageVars = await dbStorage.variables.toArray();
- autocompleteState.common.globalData = parseJSON(
- workflow.value.globalData,
- {}
- );
- autocompleteState.common.variables = {};
- storageVars.forEach((variable) => {
- autocompleteState.common.variables[`$$${variable.name}`] = {};
- });
- } catch (error) {
- console.error(error);
- }
- }
- function registerTrigger() {
- const triggerBlock = editorData.value.nodes.find(
- (node) => node.label === 'trigger'
- );
- registerWorkflowTrigger(workflowId, triggerBlock);
- }
- function executeCommand(type) {
- state.isExecuteCommand = true;
- if (type === 'undo') {
- commandManager.undo();
- } else if (type === 'redo') {
- commandManager.redo();
- }
- clearTimeout(executeCommandTimeout);
- setTimeout(() => {
- state.isExecuteCommand = false;
- }, 500);
- }
- function onNodesChange(changes) {
- const nodeChanges = { added: [], removed: [] };
- changes.forEach(({ type, id, item }) => {
- if (type === 'remove') {
- if (editState.blockData.blockId === id) {
- editState.editing = false;
- editState.blockData = {};
- }
- state.dataChanged = true;
- nodeChanges.removed.push(id);
- } else if (type === 'add') {
- if (isPackage) {
- const excludeBlocks = ['block-package', 'trigger', 'execute-workflow'];
- if (excludeBlocks.includes(item.label)) {
- editor.value.removeNodes([item]);
- }
- return;
- }
- nodeChanges.added.push(item);
- }
- });
- if (state.isExecuteCommand) return;
- let command = null;
- if (nodeChanges.added.length > 0) {
- command = editorCommands.nodeAdded(nodeChanges.added);
- } else if (nodeChanges.removed.length > 0) {
- command = editorCommands.nodeRemoved(nodeChanges.removed);
- }
- if (command) {
- commandManager.add(command);
- }
- }
- function autoAlign() {
- state.animateBlocks = true;
- const graph = new dagre.graphlib.Graph();
- graph.setGraph({
- rankdir: 'LR',
- ranksep: 100,
- ranker: 'tight-tree',
- });
- graph._isMultigraph = true;
- graph.setDefaultEdgeLabel(() => ({}));
- editor.value.getNodes.value.forEach(
- ({ id, label, dimensions, parentNode }) => {
- if (label === 'blocks-group-2' || parentNode) return;
- graph.setNode(id, {
- label,
- width: dimensions.width,
- height: dimensions.height,
- });
- }
- );
- editor.value.getEdges.value.forEach(({ source, target, id }) => {
- graph.setEdge(source, target, { id });
- });
- dagre.layout(graph);
- const nodeChanges = [];
- graph.nodes().forEach((nodeId) => {
- const graphNode = graph.node(nodeId);
- if (!graphNode) return;
- const { x, y } = graphNode;
- if (editorCommands.state.nodes[nodeId]) {
- editorCommands.state.nodes[nodeId].position = { x, y };
- }
- nodeChanges.push({
- id: nodeId,
- type: 'position',
- dragging: false,
- position: { x, y },
- });
- });
- editor.value.applyNodeChanges(nodeChanges);
- editor.value.fitView();
- setTimeout(() => {
- state.dataChanged = true;
- state.animateBlocks = false;
- }, 500);
- }
- function toggleSidebar() {
- state.showSidebar = !state.showSidebar;
- localStorage.setItem('workflow:sidebar', state.showSidebar);
- }
- function initEditBlock(data) {
- const { editComponent, data: blockDefData, name } = blocks[data.id];
- const blockData = defu(data.data, blockDefData);
- const blockEditComponent =
- typeof editComponent === 'string' ? editComponent : markRaw(editComponent);
- editState.blockData = {
- ...data,
- editComponent: blockEditComponent,
- name,
- data: blockData,
- };
- if (data.id === 'wait-connections') {
- const connections = editor.value.getEdges.value.reduce(
- (acc, { target, sourceNode, source }) => {
- if (target !== data.blockId) return acc;
- const blockNameKey = `workflow.blocks.${sourceNode.label}.name`;
- let blockName = te(blockNameKey)
- ? t(blockNameKey)
- : blocks[sourceNode.label].name;
- const { description, name: groupName } = sourceNode.data;
- if (description || groupName)
- blockName += ` (${description || groupName})`;
- acc.push({
- id: source,
- name: blockName,
- });
- return acc;
- },
- []
- );
- editState.blockData.connections = connections;
- }
- state.showSidebar = true;
- editState.editing = true;
- }
- async function updateWorkflow(data) {
- try {
- if (isPackage) {
- if (workflow.value.isExternal) return;
- delete data.drawflow;
- await packageStore.update({
- id: workflowId,
- data,
- });
- return;
- }
- if (isTeamWorkflow) {
- if (!haveEditAccess.value && !data.globalData) return;
- await teamWorkflowStore.update({
- data,
- teamId,
- id: workflowId,
- });
- } else {
- await workflowStore.update({
- data,
- id: route.params.id,
- });
- }
- workflowPayload.data = { ...workflowPayload.data, ...data };
- if (!isTeamWorkflow) await updateHostedWorkflow();
- } catch (error) {
- console.error(error);
- }
- }
- function onActionUpdated({ data, changedIndicator }) {
- state.dataChanged = changedIndicator;
- workflowPayload.data = { ...workflowPayload.data, ...data };
- if (!isPackage) updateHostedWorkflow();
- }
- function onEditorInit(instance) {
- editor.value = instance;
- let nodeToConnect = null;
- instance.onEdgesChange(onEdgesChange);
- instance.onNodesChange(onNodesChange);
- instance.onEdgeDoubleClick(({ edge }) => {
- instance.removeEdges([edge]);
- });
- instance.onConnectStart(({ nodeId, handleId, handleType }) => {
- if (handleType !== 'source') return;
- nodeToConnect = { nodeId, handleId };
- });
- instance.onConnectEnd(({ target }) => {
- if (!nodeToConnect) return;
- const isNotTargetHandle = !target.closest('.vue-flow__handle.target');
- const targetNode = isNotTargetHandle && target.closest('.vue-flow__node');
- if (targetNode && targetNode.dataset.id !== nodeToConnect.nodeId) {
- const nodeId = targetNode.dataset.id;
- const nodeData = editor.value.getNode.value(nodeId);
- if (nodeData && nodeData.handleBounds.target.length >= 1) {
- const targetHandle = nodeData.handleBounds.target.find(
- (item) => item.id
- );
- if (!targetHandle) return;
- editor.value.addEdges([
- {
- target: nodeId,
- source: nodeToConnect.nodeId,
- targetHandle: targetHandle.id,
- sourceHandle: nodeToConnect.handleId,
- },
- ]);
- }
- }
- nodeToConnect = null;
- });
- // instance.onEdgeUpdateEnd(({ edge }) => {
- // editorCommands.state.edges[edge.id] = edge;
- // });
- instance.onNodeDragStop(({ nodes }) => {
- if (!editorCommands?.state?.nodes) return;
- nodes.forEach((node) => {
- editorCommands.state.nodes[node.id] = node;
- });
- });
- instance.removeSelectedNodes(
- instance.getSelectedNodes.value.map(({ id }) => id)
- );
- instance.removeSelectedEdges(
- instance.getSelectedEdges.value.map(({ id }) => id)
- );
- const editorContainer = document.querySelector(
- '.vue-flow__viewport.vue-flow__container'
- );
- editorContainer.addEventListener('click', onClickEditor);
- const convertToObj = (array) =>
- array.reduce((acc, item) => {
- acc[item.id] = item;
- return acc;
- }, {});
- setTimeout(() => {
- const commandInitState = {
- nodes: convertToObj(instance.getNodes.value),
- edges: convertToObj(instance.getEdges.value),
- };
- editorCommands = new EditorCommands(instance, commandInitState);
- }, 1000);
- const { blockId } = route.query;
- if (blockId) goToBlock(blockId);
- }
- function clearHighlightedElements() {
- const elements = document.querySelectorAll(
- '.dropable-area__node, .dropable-area__handle'
- );
- elements.forEach((element) => {
- element.classList.remove('dropable-area__node');
- element.classList.remove('dropable-area__handle');
- });
- }
- function toggleHighlightElement({ target, elClass, classes }) {
- const targetEl = target.closest(elClass);
- if (targetEl) {
- targetEl.classList.add(classes);
- } else {
- const elements = document.querySelectorAll(`.${classes}`);
- elements.forEach((element) => {
- element.classList.remove(classes);
- });
- }
- }
- function onDragoverEditor({ target }) {
- toggleHighlightElement({
- target,
- elClass: '.vue-flow__handle.source',
- classes: 'dropable-area__handle',
- });
- if (!target.closest('.vue-flow__handle')) {
- toggleHighlightElement({
- target,
- elClass: '.vue-flow__node:not(.vue-flow__node-BlockGroup)',
- classes: 'dropable-area__node',
- });
- }
- }
- function onDropInEditor({ dataTransfer, clientX, clientY, target }) {
- const savedBlocks = parseJSON(dataTransfer.getData('savedBlocks'), null);
- if (savedBlocks && !isPackage) {
- if (savedBlocks.settings.asBlock) {
- const position = editor.value.project({
- x: clientX - 360,
- y: clientY - 18,
- });
- editor.value.addNodes([
- {
- position,
- id: nanoid(),
- data: savedBlocks,
- type: 'BlockPackage',
- label: 'block-package',
- },
- ]);
- } else {
- const { nodes, edges } = savedBlocks.data;
- /* eslint-disable-next-line */
- const newElements = copyElements(nodes, edges, { clientX, clientY });
- editor.value.addNodes(newElements.nodes);
- editor.value.addEdges(newElements.edges);
- }
- state.dataChanged = true;
- return;
- }
- const block = parseJSON(dataTransfer.getData('block'), null);
- if (!block) return;
- if (block.id === 'trigger' && isPackage) return;
- clearHighlightedElements();
- const isTriggerExists =
- block.id === 'trigger' &&
- editor.value.getNodes.value.some((node) => node.label === 'trigger');
- if (isTriggerExists) return;
- const nodeEl = DroppedNode.isNode(target);
- if (nodeEl) {
- DroppedNode.replaceNode(editor.value, { block, target: nodeEl });
- return;
- }
- const position = editor.value.project({ x: clientX - 360, y: clientY - 18 });
- const nodeId = nanoid();
- const newNode = {
- position,
- label: block.id,
- data: block.data,
- type: block.component,
- id: block.id === 'blocks-group-2' ? `group-${nodeId}` : nodeId,
- };
- editor.value.addNodes([newNode]);
- const edgeEl = DroppedNode.isEdge(target);
- const handleEl = DroppedNode.isHandle(target);
- if (handleEl) {
- DroppedNode.appendNode(editor.value, {
- target: handleEl,
- nodeId: newNode.id,
- });
- } else if (edgeEl) {
- DroppedNode.insertBetweenNode(editor.value, {
- target: edgeEl,
- nodeId: newNode.id,
- outputs: block.outputs,
- });
- }
- if (block.fromGroup) {
- setTimeout(() => {
- const blockEl = document.querySelector(`[data-id="${newNode.id}"]`);
- blockEl?.setAttribute('group-item-id', block.itemId);
- }, 200);
- }
- state.dataChanged = true;
- }
- function copyElements(nodes, edges, initialPos) {
- const newIds = new Map();
- let firstNodePos = null;
- const newNodes = nodes.map(({ id, label, position, data, type }, index) => {
- const newNodeId = nanoid();
- const nodePos = {
- z: position.z || 0,
- y: position.y + 50,
- x: position.x + 50,
- };
- newIds.set(id, newNodeId);
- if (initialPos) {
- if (index === 0) {
- firstNodePos = {
- x: nodePos.x,
- y: nodePos.y,
- };
- initialPos = editor.value.project({
- y: initialPos.clientY,
- x: initialPos.clientX - 360,
- });
- Object.assign(nodePos, initialPos);
- } else {
- const xDistance = nodePos.x - firstNodePos.x;
- const yDistance = nodePos.y - firstNodePos.y;
- nodePos.x = initialPos.x + xDistance;
- nodePos.y = initialPos.y + yDistance;
- }
- }
- const copyNode = cloneDeep({
- data,
- label,
- id: newNodeId,
- selected: true,
- position: nodePos,
- type: type || blocks[label].component,
- });
- copyNode.data = reactive(copyNode.data);
- return copyNode;
- });
- const newEdges = edges.reduce(
- (acc, { target, targetHandle, source, sourceHandle }) => {
- const targetId = newIds.get(target);
- const sourceId = newIds.get(source);
- if (!targetId || !sourceId) return acc;
- const copyEdge = cloneDeep({
- selected: true,
- target: targetId,
- source: sourceId,
- id: `edge-${nanoid()}`,
- targetHandle: targetHandle.replace(target, targetId),
- sourceHandle: sourceHandle.replace(source, sourceId),
- });
- acc.push(copyEdge);
- return acc;
- },
- []
- );
- return {
- nodes: newNodes,
- edges: newEdges,
- };
- }
- function duplicateElements({ nodes, edges }) {
- const selectedNodes = editor.value.getSelectedNodes.value;
- const selectedEdges = editor.value.getSelectedEdges.value;
- const { edges: newEdges, nodes: newNodes } = copyElements(
- nodes || selectedNodes,
- edges || selectedEdges
- );
- selectedNodes.forEach((node) => {
- node.selected = false;
- });
- selectedEdges.forEach((edge) => {
- edge.selected = false;
- });
- editor.value.addNodes(newNodes);
- editor.value.addEdges(newEdges);
- state.dataChanged = true;
- }
- function copySelectedElements(data = {}) {
- const nodes = data.nodes || editor.value.getSelectedNodes.value;
- const edges = data.edges || editor.value.getSelectedEdges.value;
- const clipboardData = JSON.stringify({
- name: 'automa-blocks',
- data: { nodes, edges },
- });
- navigator.clipboard.writeText(clipboardData).catch((error) => {
- console.error(error);
- });
- }
- async function pasteCopiedElements(position) {
- editor.value.removeSelectedNodes(editor.value.getSelectedNodes.value);
- editor.value.removeSelectedEdges(editor.value.getSelectedEdges.value);
- const permission = await browser.permissions.request({
- permissions: ['clipboardRead'],
- });
- if (!permission) {
- toast.error('Automa require clipboard permission to paste blocks');
- return;
- }
- try {
- const copiedText = await navigator.clipboard.readText();
- const workflowBlocks = parseJSON(copiedText);
- if (workflowBlocks && workflowBlocks.name === 'automa-blocks') {
- const { nodes, edges } = copyElements(
- workflowBlocks.data.nodes,
- workflowBlocks.data.edges,
- position
- );
- editor.value.addNodes(nodes);
- editor.value.addEdges(edges);
- state.dataChanged = true;
- return;
- }
- } catch (error) {
- console.error(error);
- }
- }
- function undoRedoCommand(type, { target }) {
- const els = ['INPUT', 'SELECT', 'TEXTAREA'];
- if (els.includes(target.tagName) || target.isContentEditable) return;
- executeCommand(type);
- }
- function onKeydown({ ctrlKey, metaKey, shiftKey, key, target }) {
- const els = ['INPUT', 'SELECT', 'TEXTAREA'];
- if (
- els.includes(target.tagName) ||
- target.isContentEditable ||
- !target.classList.contains('workflow-editor')
- )
- return;
- if (isPackage && workflow.value.isExternal) return;
- const command = (keyName) => (ctrlKey || metaKey) && keyName === key;
- if (command('c')) {
- copySelectedElements();
- } else if (command('v')) {
- pasteCopiedElements();
- } else if (command('z')) {
- undoRedoCommand(shiftKey ? 'redo' : 'undo');
- }
- }
- async function fetchConnectedTable() {
- const table = await dbStorage.tablesItems
- .where('id')
- .equals(workflow.value.connectedTable)
- .first();
- if (!table) return;
- connectedTable.value = table;
- }
- function checkWorkflowPermission() {
- getWorkflowPermissions(editorData.value).then((permissions) => {
- if (permissions.length === 0) return;
- permissionState.items = permissions;
- permissionState.showModal = true;
- });
- }
- function checkWorkflowUpdate() {
- const updatedAt = encodeURIComponent(workflow.value.updatedAt);
- fetchApi(
- `/teams/${teamId}/workflows/${workflowId}/check-update?updatedAt=${updatedAt}`
- )
- .then((response) => response.json())
- .then((result) => {
- if (!result) return;
- updateWorkflow(result).then(() => {
- editor.value.setNodes(result.drawflow.nodes || []);
- editor.value.setEdges(result.drawflow.edges || []);
- editor.value.fitView();
- });
- })
- .catch((error) => {
- console.error(error);
- });
- }
- useHead({
- title: () => `${workflow.value?.name} workflow - Automa` || 'Automa',
- });
- const shortcut = useShortcut([
- getShortcut('editor:toggle-sidebar', toggleSidebar),
- getShortcut('editor:duplicate-block', duplicateElements),
- ]);
- watch(
- () => state.activeTab,
- (value) => {
- router.replace({ ...route, query: { tab: value } });
- }
- );
- watch(
- () => route.params.id,
- (value, oldValue) => {
- if (route.name !== 'workflows-details') return;
- if (value && oldValue && value !== oldValue) {
- window.location.reload();
- }
- }
- );
- /* eslint-disable consistent-return */
- onBeforeRouteLeave(() => {
- updateHostedWorkflow();
- const dataNotChanged = !state.dataChanged || !haveEditAccess.value;
- const isExternalPkg = isPackage && workflow.value.isExternal;
- if (dataNotChanged || isExternalPkg) return;
- const confirm = window.confirm(t('message.notSaved'));
- if (!confirm) return false;
- });
- onMounted(() => {
- if (!workflow.value) {
- router.replace(isPackage ? '/packages' : '/');
- return null;
- }
- state.showSidebar =
- JSON.parse(localStorage.getItem('workflow:sidebar')) ?? true;
- if (!isPackage) {
- const convertedData = convertWorkflowData(workflow.value);
- updateWorkflow({ drawflow: convertedData.drawflow }).then(() => {
- state.workflowConverted = true;
- });
- } else {
- state.workflowConverted = true;
- }
- if (route.query.permission || (isTeamWorkflow && !haveEditAccess.value))
- checkWorkflowPermission();
- if (isTeamWorkflow && !haveEditAccess.value && workflow.value.updatedAt) {
- checkWorkflowUpdate();
- }
- if (workflow.value.connectedTable) {
- fetchConnectedTable();
- }
- initAutocomplete();
- window.onbeforeunload = () => {
- if (isPackage && workflow.value.isExternal) return;
- updateHostedWorkflow();
- if (state.dataChanged && haveEditAccess.value) {
- return t('message.notSaved');
- }
- };
- window.addEventListener('keydown', onKeydown);
- });
- onBeforeUnmount(() => {
- const editorContainer = document.querySelector(
- '.vue-flow__viewport.vue-flow__container'
- );
- if (editorContainer)
- editorContainer.removeEventListener('click', onClickEditor);
- window.onbeforeunload = null;
- window.removeEventListener('keydown', onKeydown);
- });
- </script>
- <style>
- .vue-flow,
- .editor-tab {
- width: 100%;
- height: 100%;
- }
- .vue-flow__node {
- @apply rounded-lg;
- }
- .dropable-area__node,
- .dropable-area__handle {
- @apply ring-4;
- }
- .animate-blocks {
- .vue-flow__transformationpane,
- .vue-flow__node {
- transition: transform 300ms ease;
- }
- }
- .undo-redo {
- button:not(:disabled):hover {
- @apply bg-box-transparent;
- }
- button:disabled {
- @apply text-gray-500 dark:text-gray-400;
- }
- }
- </style>
|